home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / src / mail / pine3.96.tar.gz / pine3.96.tar / pine3.96 / imap / ANSI / c-client / nntpcmac.c < prev    next >
C/C++ Source or Header  |  1996-05-02  |  35KB  |  1,188 lines

  1. /*
  2.  * Program:    Network News Transfer Protocol (NNTP) client routines for Mac
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    12 December 1993
  13.  * Last Edited:    2 May 1996
  14.  *
  15.  * Copyright 1996 by Mark Crispin
  16.  *
  17.  *  Permission to use, copy, modify, and distribute this software and its
  18.  * documentation for any purpose and without fee is hereby granted, provided
  19.  * that the above copyright notice appears in all copies and that both the
  20.  * above copyright notices and this permission notice appear in supporting
  21.  * documentation, and that the name of Mark Crispin not be used in advertising
  22.  * or publicity pertaining to distribution of the software without specific,
  23.  * written prior permission.  This software is made available "as is", and
  24.  * MARK CRISPIN DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO
  25.  * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF
  26.  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL
  27.  * MARK CRISPIN BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES
  28.  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  29.  * WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT
  30.  * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
  31.  * THIS SOFTWARE.
  32.  *
  33.  */
  34.  
  35.  
  36. #include "mail.h"
  37. #include "osdep.h"
  38. #include <ctype.h>
  39. #include <stdio.h>
  40. #include <errno.h>
  41. extern int errno;        /* just in case */
  42. #include "smtp.h"
  43. #include "nntp.h"
  44. #include "nntpcmac.h"
  45. #include "rfc822.h"
  46. #include "misc.h"
  47. #include "newsrc.h"
  48.  
  49. /* NNTP mail routines */
  50.  
  51.  
  52. /* Driver dispatch used by MAIL */
  53.  
  54. DRIVER nntpdriver = {
  55.   "nntp",            /* driver name */
  56.   (DRIVER *) NIL,        /* next driver */
  57.   nntp_valid,            /* mailbox is valid for us */
  58.   nntp_parameters,        /* manipulate parameters */
  59.   nntp_find,            /* find mailboxes */
  60.   nntp_find_bboards,        /* find bboards */
  61.   nntp_find_all,        /* find all mailboxes */
  62.   nntp_find_all_bboards,    /* find all bboards */
  63.   nntp_subscribe,        /* subscribe to mailbox */
  64.   nntp_unsubscribe,        /* unsubscribe from mailbox */
  65.   nntp_subscribe_bboard,    /* subscribe to bboard */
  66.   nntp_unsubscribe_bboard,    /* unsubscribe from bboard */
  67.   nntp_create,            /* create mailbox */
  68.   nntp_delete,            /* delete mailbox */
  69.   nntp_rename,            /* rename mailbox */
  70.   nntp_mopen,            /* open mailbox */
  71.   nntp_close,            /* close mailbox */
  72.   nntp_fetchfast,        /* fetch message "fast" attributes */
  73.   nntp_fetchflags,        /* fetch message flags */
  74.   nntp_fetchstructure,        /* fetch message envelopes */
  75.   nntp_fetchheader,        /* fetch message header only */
  76.   nntp_fetchtext,        /* fetch message body only */
  77.   nntp_fetchbody,        /* fetch message body section */
  78.   nntp_setflag,            /* set message flag */
  79.   nntp_clearflag,        /* clear message flag */
  80.   nntp_search,            /* search for message based on criteria */
  81.   nntp_ping,            /* ping mailbox to see if still alive */
  82.   nntp_check,            /* check for new messages */
  83.   nntp_expunge,            /* expunge deleted messages */
  84.   nntp_copy,            /* copy messages to another mailbox */
  85.   nntp_move,            /* move messages to another mailbox */
  86.   nntp_append,            /* append string message to mailbox */
  87.   nntp_gc            /* garbage collect stream */
  88. };
  89.  
  90.                 /* prototype stream */
  91. MAILSTREAM nntpproto = {&nntpdriver};
  92.  
  93. /* NNTP mail validate mailbox
  94.  * Accepts: mailbox name
  95.  * Returns: our driver if name is valid, NIL otherwise
  96.  */
  97.  
  98. DRIVER *nntp_valid (char *name)
  99. {
  100.                 /* must be bboard */
  101.   return *name == '*' ? mail_valid_net (name,&nntpdriver,NIL,NIL) : NIL;
  102. }
  103.  
  104.  
  105. /* News manipulate driver parameters
  106.  * Accepts: function code
  107.  *        function-dependent value
  108.  * Returns: function-dependent return value
  109.  */
  110.  
  111. void *nntp_parameters (long function,void *value)
  112. {
  113.   return NIL;
  114. }
  115.  
  116. /* NNTP mail find list of mailboxes
  117.  * Accepts: mail stream
  118.  *        pattern to search
  119.  */
  120.  
  121. void nntp_find (MAILSTREAM *stream,char *pat)
  122. {
  123.   /* Always a no-op */
  124. }
  125.  
  126.  
  127. /* NNTP mail find list of bboards
  128.  * Accepts: mail stream
  129.  *        pattern to search
  130.  */
  131.  
  132. void nntp_find_bboards (MAILSTREAM *stream,char *pat)
  133. {
  134.   char *t;
  135.   void *s = NIL;
  136.   if (stream) newsrc_find (pat);/* use .newsrc if a stream given */
  137.   else while (t = sm_read (&s))    /* use subscription manager if no stream */
  138.     if ((*t == '*') && pmatch (t+1,pat)) mm_bboard (t+1);
  139. }
  140.  
  141. /* NNTP mail find list of all mailboxes
  142.  * Accepts: mail stream
  143.  *        pattern to search
  144.  */
  145.  
  146. void nntp_find_all (MAILSTREAM *stream,char *pat)
  147. {
  148.   /* Always a no-op */
  149. }
  150.  
  151.  
  152. /* NNTP mail find list of all bboards
  153.  * Accepts: mail stream
  154.  *        pattern to search
  155.  */
  156.  
  157. void nntp_find_all_bboards (MAILSTREAM *stream,char *pat)
  158. {
  159.   char *s,*t,*bbd,*patx,tmp[MAILTMPLEN];
  160.                 /* use .newsrc if a stream given */
  161.   if (stream && LOCAL && LOCAL->nntpstream) {
  162.                 /* begin with a host specification? */
  163.     if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
  164.     (t = strchr (pat,'}')) && *(patx = ++t)) {
  165.       if (*pat == '*') pat++;    /* yes, skip leading * (old Pine behavior) */
  166.       strcpy (tmp,pat);        /* copy host name */
  167.       bbd = tmp + (patx - pat);    /* where we write the bboards */
  168.     }
  169.     else {            /* no host specification */
  170.       bbd = tmp;        /* no prefix */
  171.       patx = pat;        /* use entire specification */
  172.     }
  173.                 /* ask server for all active newsgroups */
  174.     if (!(nntp_send (LOCAL->nntpstream,"LIST","ACTIVE") == NNTPGLIST)) return;
  175.                 /* process data until we see final dot */
  176.     while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) && *s != '.') {
  177.                 /* tie off after newsgroup name */
  178.       if (t = strchr (s,' ')) *t = '\0';
  179.       if (pmatch (s,patx)) {    /* report to main program if have match */
  180.     strcpy (bbd,s);        /* write newsgroup name after prefix */
  181.     mm_bboard (tmp);
  182.       }
  183.       fs_give ((void **) &s);    /* clean up */
  184.     }
  185.   }
  186. }
  187.  
  188. /* NNTP mail subscribe to mailbox
  189.  * Accepts: mail stream
  190.  *        mailbox to add to subscription list
  191.  * Returns: T on success, NIL on failure
  192.  */
  193.  
  194. long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
  195. {
  196.   return NIL;            /* never valid for NNTP */
  197. }
  198.  
  199.  
  200. /* NNTP mail unsubscribe to mailbox
  201.  * Accepts: mail stream
  202.  *        mailbox to delete from subscription list
  203.  * Returns: T on success, NIL on failure
  204.  */
  205.  
  206. long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
  207. {
  208.   return NIL;            /* never valid for NNTP */
  209. }
  210.  
  211.  
  212. /* NNTP mail subscribe to bboard
  213.  * Accepts: mail stream
  214.  *        bboard to add to subscription list
  215.  * Returns: T on success, NIL on failure
  216.  */
  217.  
  218. long nntp_subscribe_bboard (MAILSTREAM *stream,char *mailbox)
  219. {
  220.   char *s = strchr (mailbox,'}');
  221.   return s ? newsrc_update (s+1,':') : NIL;
  222. }
  223.  
  224.  
  225. /* NNTP mail unsubscribe to bboard
  226.  * Accepts: mail stream
  227.  *        bboard to delete from subscription list
  228.  * Returns: T on success, NIL on failure
  229.  */
  230.  
  231. long nntp_unsubscribe_bboard (MAILSTREAM *stream,char *mailbox)
  232. {
  233.   char *s = strchr (mailbox,'}');
  234.   return s ? newsrc_update (s+1,'!') : NIL;
  235. }
  236.  
  237. /* NNTP mail create mailbox
  238.  * Accepts: mail stream
  239.  *        mailbox name to create
  240.  * Returns: T on success, NIL on failure
  241.  */
  242.  
  243. long nntp_create (MAILSTREAM *stream,char *mailbox)
  244. {
  245.   return NIL;            /* never valid for NNTP */
  246. }
  247.  
  248.  
  249. /* NNTP mail delete mailbox
  250.  *        mailbox name to delete
  251.  * Returns: T on success, NIL on failure
  252.  */
  253.  
  254. long nntp_delete (MAILSTREAM *stream,char *mailbox)
  255. {
  256.   return NIL;            /* never valid for NNTP */
  257. }
  258.  
  259.  
  260. /* NNTP mail rename mailbox
  261.  * Accepts: mail stream
  262.  *        old mailbox name
  263.  *        new mailbox name
  264.  * Returns: T on success, NIL on failure
  265.  */
  266.  
  267. long nntp_rename (MAILSTREAM *stream,char *old,char *new)
  268. {
  269.   return NIL;            /* never valid for NNTP */
  270. }
  271.  
  272. /* NNTP mail open
  273.  * Accepts: stream to open
  274.  * Returns: stream on success, NIL on failure
  275.  */
  276.  
  277. MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
  278. {
  279.   long i,j,k;
  280.   long nmsgs = 0;
  281.   long unseen = 0;
  282.   char c = NIL,*s,*t,tmp[MAILTMPLEN];
  283.   NETMBX mb;
  284.   struct hostent *host_name;
  285.   void *tcpstream;
  286.   SMTPSTREAM *nstream = NIL;
  287.                 /* return prototype for OP_PROTOTYPE call */
  288.   if (!stream) return &nntpproto;
  289.   mail_valid_net_parse (stream->mailbox,&mb);
  290.   if (!*mb.mailbox) strcpy (mb.mailbox,"general");
  291.   if (LOCAL) {            /* if recycle stream, see if changing hosts */
  292.     if (strcmp (lcase (mb.host),lcase (strcpy (tmp,LOCAL->host)))) {
  293.       sprintf (tmp,"Closing connection to %s",LOCAL->host);
  294.       if (!stream->silent) mm_log (tmp,(long) NIL);
  295.     }
  296.     else {            /* same host, preserve NNTP connection */
  297.       sprintf (tmp,"Reusing connection to %s",LOCAL->host);
  298.       if (!stream->silent) mm_log (tmp,(long) NIL);
  299.       nstream = LOCAL->nntpstream;
  300.       LOCAL->nntpstream = NIL;    /* keep nntp_close() from punting it */
  301.     }
  302.     nntp_close (stream);    /* do close action */
  303.     stream->dtb = &nntpdriver;/* reattach this driver */
  304.     mail_free_cache (stream);    /* clean up cache */
  305.   }
  306.                 /* in case /debug switch given */
  307.   if (mb.dbgflag) stream->debug = T;
  308.  
  309.   if (!nstream) {        /* open NNTP now if not already open */
  310.     char *hostlist[2];
  311.     hostlist[0] = strcpy (tmp,mb.host);
  312.     if (mb.port) sprintf (tmp + strlen (tmp),":%ld",mb.port);
  313.     hostlist[1] = NIL;
  314.     nstream = nntp_open (hostlist,OP_READONLY+(stream->debug ? OP_DEBUG:NIL));
  315.   }
  316.   if (nstream) {        /* now try to open newsgroup */
  317.     if ((!stream->halfopen) &&    /* open the newsgroup if not halfopen */
  318.     ((nntp_send (nstream,"GROUP",mb.mailbox) != NNTPGOK) ||
  319.      ((k = strtol (nstream->reply + 4,&s,10)) < 0) ||
  320.      ((i = strtol (s,&s,10)) < 0) || ((j = strtol (s,&s,10)) < 0) ||
  321.      (nmsgs = i | j ? 1 + j - i : 0) < 0)) {
  322.       mm_log (nstream->reply,ERROR);
  323.       smtp_close (nstream);    /* punt stream */
  324.       nstream = NIL;
  325.       return NIL;
  326.     }
  327.                 /* newsgroup open, instantiate local data */
  328.     stream->local = fs_get (sizeof (NNTPLOCAL));
  329.     LOCAL->nntpstream = nstream;
  330.     LOCAL->dirty = NIL;        /* no update to .newsrc needed yet */
  331.                 /* copy host and newsgroup name */
  332.     LOCAL->host = cpystr (mb.host);
  333.     LOCAL->name = cpystr (mb.mailbox);
  334.     stream->sequence++;        /* bump sequence number */
  335.     stream->rdonly = T;        /* make sure higher level knows readonly */
  336.     LOCAL->number = NIL;
  337.     LOCAL->header = LOCAL->body = NIL;
  338.     LOCAL->buf = NIL;
  339.  
  340.     if (!stream->halfopen) {    /* if not half-open */
  341.       if (nmsgs) {        /* find what messages exist */
  342.     LOCAL->number = (unsigned long *) fs_get (nmsgs*sizeof(unsigned long));
  343.     sprintf (tmp,"%ld-%ld",i,j);
  344.     if ((nntp_send (nstream,"LISTGROUP",mb.mailbox) == NNTPGOK) ||
  345.         (nntp_send (nstream,"XHDR Date",tmp) == NNTPHEAD)) {
  346.       for (i = 0; (s = tcp_getline (nstream->tcpstream)) && strcmp (s,".");
  347.            i++) {        /* initialize c-client/NNTP map */
  348.         if (i < nmsgs) LOCAL->number[i] = atol (s);
  349.         fs_give ((void **) &s);
  350.       }
  351.       if (s) fs_give ((void **) &s);
  352.       if (i != nmsgs) {    /* found holes? */
  353.         if (nmsgs = i)
  354.           fs_resize ((void **) &LOCAL->number,nmsgs*sizeof(unsigned long));
  355.         else fs_give ((void **) &LOCAL->number);
  356.       }
  357.     }
  358.                 /* assume c-client/NNTP map is entire range */
  359.     else for (k = 0; k < nmsgs; ++k) LOCAL->number[k] = i + k;
  360.                 /* create caches */
  361.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  362.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  363.                 /* initialize per-message cache */
  364.     for (i = 0; i < nmsgs; ++i) LOCAL->header[i] = LOCAL->body[i] = NIL;
  365.       }
  366.                 /* make temporary buffer */
  367.       LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  368.                 /* notify upper level that messages exist */
  369.       mail_exists (stream,nmsgs);
  370.                 /* read .newsrc entries */
  371.       mail_recent (stream,newsrc_read (LOCAL->name,stream,LOCAL->number));
  372.                 /* notify if empty bboard */
  373.       if (!(stream->nmsgs || stream->silent)) {
  374.     sprintf (tmp,"Newsgroup %s is empty",LOCAL->name);
  375.     mm_log (tmp,WARN);
  376.       }
  377.     }
  378.   }
  379.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  380. }
  381.  
  382. /* NNTP mail close
  383.  * Accepts: MAIL stream
  384.  */
  385.  
  386. void nntp_close (MAILSTREAM *stream)
  387. {
  388.   if (LOCAL) {            /* only if a file is open */
  389.     nntp_check (stream);    /* dump final checkpoint */
  390.     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
  391.     if (LOCAL->host) fs_give ((void **) &LOCAL->host);
  392.     nntp_gc (stream,GC_TEXTS);    /* free local cache */
  393.     if (LOCAL->number) fs_give ((void **) &LOCAL->number);
  394.     if (LOCAL->header) fs_give ((void **) &LOCAL->header);
  395.     if (LOCAL->body) fs_give ((void **) &LOCAL->body);
  396.                 /* free local scratch buffer */
  397.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  398.                 /* close NNTP connection */
  399.     if (LOCAL->nntpstream) smtp_close (LOCAL->nntpstream);
  400.                 /* nuke the local data */
  401.     fs_give ((void **) &stream->local);
  402.     stream->dtb = NIL;        /* log out the DTB */
  403.   }
  404. }
  405.  
  406. /* NNTP mail fetch fast information
  407.  * Accepts: MAIL stream
  408.  *        sequence
  409.  */
  410.  
  411. void nntp_fetchfast (MAILSTREAM *stream,char *sequence)
  412. {
  413.   long i;
  414.   BODY *b;
  415.                 /* ugly and slow */
  416.   if (stream && LOCAL && mail_sequence (stream,sequence))
  417.     for (i = 1; i <= stream->nmsgs; i++)
  418.       if (mail_elt (stream,i)->sequence)
  419.     nntp_fetchstructure (stream,i,&b);
  420. }
  421.  
  422.  
  423. /* NNTP mail fetch flags
  424.  * Accepts: MAIL stream
  425.  *        sequence
  426.  */
  427.  
  428. void nntp_fetchflags (MAILSTREAM *stream,char *sequence)
  429. {
  430.   return;            /* no-op for local mail */
  431. }
  432.  
  433. /* NNTP mail fetch envelope
  434.  * Accepts: MAIL stream
  435.  *        message # to fetch
  436.  *        pointer to return body
  437.  * Returns: envelope of this message, body returned in body value
  438.  *
  439.  * Fetches the "fast" information as well
  440.  */
  441.  
  442. ENVELOPE *nntp_fetchstructure (MAILSTREAM *stream,long msgno,BODY **body)
  443. {
  444.   char *h,*t;
  445.   LONGCACHE *lelt;
  446.   ENVELOPE **env;
  447.   STRING bs;
  448.   BODY **b;
  449.   unsigned long hdrsize;
  450.   unsigned long textsize = 0;
  451.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  452.   if (stream->scache) {        /* short cache */
  453.     if (msgno != stream->msgno){/* flush old poop if a different message */
  454.       mail_free_envelope (&stream->env);
  455.       mail_free_body (&stream->body);
  456.     }
  457.     stream->msgno = msgno;
  458.     env = &stream->env;        /* get pointers to envelope and body */
  459.     b = &stream->body;
  460.   }
  461.   else {            /* long cache */
  462.     lelt = mail_lelt (stream,msgno);
  463.     env = &lelt->env;        /* get pointers to envelope and body */
  464.     b = &lelt->body;
  465.   }
  466.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  467.     mail_free_envelope (env);    /* flush old envelope and body */
  468.     mail_free_body (b);
  469.     hdrsize = strlen (h = nntp_fetchheader (stream,msgno));
  470.     if (body) {            /* only if want to parse body */
  471.       textsize = strlen (t = nntp_fetchtext_work (stream,msgno));
  472.                 /* calculate message size */
  473.       elt->rfc822_size = hdrsize + textsize;
  474.       INIT (&bs,mail_string,(void *) t,textsize);
  475.     }
  476.                 /* parse envelope and body */
  477.     rfc822_parse_msg (env,body ? b : NIL,h,hdrsize,body ? &bs : NIL,BADHOST,
  478.               LOCAL->buf);
  479.                 /* parse date */
  480.     if (*env && (*env)->date) mail_parse_date (elt,(*env)->date);
  481.     if (!elt->month) mail_parse_date (elt,"01-JAN-1969 00:00:00 GMT");
  482.   }
  483.   if (body) *body = *b;        /* return the body */
  484.   return *env;            /* return the envelope */
  485. }
  486.  
  487. /* NNTP mail fetch message header
  488.  * Accepts: MAIL stream
  489.  *        message # to fetch
  490.  * Returns: message header in RFC822 format
  491.  */
  492.  
  493. char *nntp_fetchheader (MAILSTREAM *stream,long msgno)
  494. {
  495.   char tmp[MAILTMPLEN];
  496.   long m = msgno - 1;
  497.   if (!LOCAL->header[m]) {    /* fetch header if don't have already */
  498.     sprintf (tmp,"%ld",LOCAL->number[m]);
  499.     if (nntp_send (LOCAL->nntpstream,"HEAD",tmp) == NNTPHEAD)
  500.       LOCAL->header[m] = nntp_slurp (stream);
  501.                 /* failed, mark as deleted */
  502.     else mail_elt (stream,msgno)->deleted = T;
  503.   }
  504.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  505. }
  506.  
  507.  
  508. /* NNTP mail fetch message text (body only)
  509.  * Accepts: MAIL stream
  510.  *        message # to fetch
  511.  * Returns: message text in RFC822 format
  512.  */
  513.  
  514. char *nntp_fetchtext (MAILSTREAM *stream,long msgno)
  515. {
  516.   long m = msgno - 1;
  517.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  518.   elt->seen = T;        /* mark as seen */
  519.   return nntp_fetchtext_work (stream,msgno);
  520. }
  521.  
  522.  
  523. /* NNTP mail fetch message text work
  524.  * Accepts: MAIL stream
  525.  *        message # to fetch
  526.  * Returns: message text in RFC822 format
  527.  */
  528.  
  529. char *nntp_fetchtext_work (MAILSTREAM *stream,long msgno)
  530. {
  531.   char tmp[MAILTMPLEN];
  532.   long m = msgno - 1;
  533.   if (!LOCAL->body[m]) {    /* fetch body if don't have already */
  534.     sprintf (tmp,"%ld",LOCAL->number[m]);
  535.     if (nntp_send (LOCAL->nntpstream,"BODY",tmp) == NNTPBODY)
  536.       LOCAL->body[m] = nntp_slurp (stream);
  537.                 /* failed, mark as deleted */
  538.     else mail_elt (stream,msgno)->deleted = T;
  539.   }
  540.   return LOCAL->body[m] ? LOCAL->body[m] : "";
  541. }
  542.  
  543. /* NNTP mail slurp NNTP dot-terminated text
  544.  * Accepts: MAIL stream
  545.  * Returns: text
  546.  */
  547.  
  548. char *nntp_slurp (MAILSTREAM *stream)
  549. {
  550.   char *s,*t;
  551.   unsigned long i;
  552.   unsigned long bufpos = 0;
  553.   while (s = tcp_getline (LOCAL->nntpstream->tcpstream)) {
  554.     if (*s == '.') {        /* possible end of text? */
  555.       if (s[1]) t = s + 1;    /* pointer to true start of line */
  556.       else {
  557.     fs_give ((void **) &s);    /* free the line */
  558.     break;            /* end of data */
  559.       }
  560.     }
  561.     else t = s;            /* want the entire line */
  562.                 /* ensure have enough room */
  563.     if (LOCAL->buflen < (bufpos + (i = strlen (t)) + 5))
  564.       fs_resize ((void **) &LOCAL->buf,LOCAL->buflen += (MAXMESSAGESIZE + 1));
  565.                 /* copy the text */
  566.     strncpy (LOCAL->buf + bufpos,t,i);
  567.     bufpos += i;        /* set new buffer position */
  568.     LOCAL->buf[bufpos++] = '\015';
  569.     LOCAL->buf[bufpos++] = '\012';
  570.     fs_give ((void **) &s);    /* free the line */
  571.   }
  572.   LOCAL->buf[bufpos++] = '\015';/* add final newline */
  573.   LOCAL->buf[bufpos++] = '\012';
  574.   LOCAL->buf[bufpos++] = '\0';    /* tie off string with NUL */
  575.   return cpystr (LOCAL->buf);    /* return copy of collected string */
  576. }
  577.  
  578. /* NNTP fetch message body as a structure
  579.  * Accepts: Mail stream
  580.  *        message # to fetch
  581.  *        section specifier
  582.  *        pointer to length
  583.  * Returns: pointer to section of message body
  584.  */
  585.  
  586. char *nntp_fetchbody (MAILSTREAM *stream,long m,char *s,unsigned long *len)
  587. {
  588.   BODY *b;
  589.   PART *pt;
  590.   unsigned long i;
  591.   char *base;
  592.   unsigned long offset = 0;
  593.   MESSAGECACHE *elt = mail_elt (stream,m);
  594.                 /* make sure have a body */
  595.   if (!(nntp_fetchstructure (stream,m,&b) && b && s && *s &&
  596.     ((i = strtol (s,&s,10)) > 0) &&
  597.     (base = nntp_fetchtext_work (stream,m))))
  598.     return NIL;
  599.   do {                /* until find desired body part */
  600.                 /* multipart content? */
  601.     if (b->type == TYPEMULTIPART) {
  602.       pt = b->contents.part;    /* yes, find desired part */
  603.       while (--i && (pt = pt->next));
  604.       if (!pt) return NIL;    /* bad specifier */
  605.                 /* note new body, check valid nesting */
  606.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  607.       offset = pt->offset;    /* get new offset */
  608.     }
  609.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  610.                 /* need to go down further? */
  611.     if (i = *s) switch (b->type) {
  612.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  613.       offset = b->contents.msg.offset;
  614.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  615.     case TYPEMULTIPART:        /* multipart, get next section */
  616.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  617.     default:            /* bogus subpart specification */
  618.       return NIL;
  619.     }
  620.   } while (i);
  621.                 /* lose if body bogus */
  622.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  623.   elt->seen = T;        /* mark as seen */
  624.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  625.               b->size.ibytes,b->encoding);
  626. }
  627.  
  628. /* NNTP mail set flag
  629.  * Accepts: MAIL stream
  630.  *        sequence
  631.  *        flag(s)
  632.  */
  633.  
  634. void nntp_setflag (MAILSTREAM *stream,char *sequence,char *flag)
  635. {
  636.   MESSAGECACHE *elt;
  637.   long i;
  638.   short f = nntp_getflags (stream,flag);
  639.   if (!f) return;        /* no-op if no flags to modify */
  640.                 /* get sequence and loop on it */
  641.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  642.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  643.       if (f&fSEEN) elt->seen=T;    /* set all requested flags */
  644.       if (f&fDELETED) {        /* deletion also purges the cache */
  645.     elt->deleted = T;    /* mark deleted */
  646.     LOCAL->dirty = T;    /* mark dirty */
  647.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  648.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  649.       }
  650.       if (f&fFLAGGED) elt->flagged = T;
  651.       if (f&fANSWERED) elt->answered = T;
  652.     }
  653. }
  654.  
  655.  
  656. /* NNTP mail clear flag
  657.  * Accepts: MAIL stream
  658.  *        sequence
  659.  *        flag(s)
  660.  */
  661.  
  662. void nntp_clearflag (MAILSTREAM *stream,char *sequence,char *flag)
  663. {
  664.   MESSAGECACHE *elt;
  665.   long i;
  666.   short f = nntp_getflags (stream,flag);
  667.   if (!f) return;        /* no-op if no flags to modify */
  668.                 /* get sequence and loop on it */
  669.   if (mail_sequence (stream,sequence)) for (i = 0; i < stream->nmsgs; i++)
  670.     if ((elt = mail_elt (stream,i + 1))->sequence) {
  671.                 /* clear all requested flags */
  672.       if (f&fSEEN) elt->seen = NIL;
  673.       if (f&fDELETED) {
  674.     elt->deleted = NIL;    /* undelete */
  675.     LOCAL->dirty = T;    /* mark stream as dirty */
  676.       }
  677.       if (f&fFLAGGED) elt->flagged = NIL;
  678.       if (f&fANSWERED) elt->answered = NIL;
  679.     }
  680. }
  681.  
  682. /* NNTP mail search for messages
  683.  * Accepts: MAIL stream
  684.  *        search criteria
  685.  */
  686.  
  687. void nntp_search (MAILSTREAM *stream,char *criteria)
  688. {
  689.   long i,n;
  690.   char *d;
  691.   search_t f;
  692.                 /* initially all searched */
  693.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  694.                 /* get first criterion */
  695.   if (criteria && (criteria = strtok (criteria," "))) {
  696.                 /* for each criterion */
  697.     for (; criteria; (criteria = strtok (NIL," "))) {
  698.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  699.       switch (*ucase (criteria)) {
  700.       case 'A':            /* possible ALL, ANSWERED */
  701.     if (!strcmp (criteria+1,"LL")) f = nntp_search_all;
  702.     else if (!strcmp (criteria+1,"NSWERED")) f = nntp_search_answered;
  703.     break;
  704.       case 'B':            /* possible BCC, BEFORE, BODY */
  705.     if (!strcmp (criteria+1,"CC"))
  706.       f = nntp_search_string (nntp_search_bcc,&d,&n);
  707.     else if (!strcmp (criteria+1,"EFORE"))
  708.       f = nntp_search_date (nntp_search_before,&n);
  709.     else if (!strcmp (criteria+1,"ODY"))
  710.       f = nntp_search_string (nntp_search_body,&d,&n);
  711.     break;
  712.       case 'C':            /* possible CC */
  713.     if (!strcmp (criteria+1,"C")) 
  714.       f = nntp_search_string (nntp_search_cc,&d,&n);
  715.     break;
  716.       case 'D':            /* possible DELETED */
  717.     if (!strcmp (criteria+1,"ELETED")) f = nntp_search_deleted;
  718.     break;
  719.       case 'F':            /* possible FLAGGED, FROM */
  720.     if (!strcmp (criteria+1,"LAGGED")) f = nntp_search_flagged;
  721.     else if (!strcmp (criteria+1,"ROM"))
  722.       f = nntp_search_string (nntp_search_from,&d,&n);
  723.     break;
  724.       case 'K':            /* possible KEYWORD */
  725.     if (!strcmp (criteria+1,"EYWORD"))
  726.       f = nntp_search_flag (nntp_search_keyword,&d);
  727.     break;
  728.       case 'N':            /* possible NEW */
  729.     if (!strcmp (criteria+1,"EW")) f = nntp_search_new;
  730.     break;
  731.  
  732.       case 'O':            /* possible OLD, ON */
  733.     if (!strcmp (criteria+1,"LD")) f = nntp_search_old;
  734.     else if (!strcmp (criteria+1,"N"))
  735.       f = nntp_search_date (nntp_search_on,&n);
  736.     break;
  737.       case 'R':            /* possible RECENT */
  738.     if (!strcmp (criteria+1,"ECENT")) f = nntp_search_recent;
  739.     break;
  740.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  741.     if (!strcmp (criteria+1,"EEN")) f = nntp_search_seen;
  742.     else if (!strcmp (criteria+1,"INCE"))
  743.       f = nntp_search_date (nntp_search_since,&n);
  744.     else if (!strcmp (criteria+1,"UBJECT"))
  745.       f = nntp_search_string (nntp_search_subject,&d,&n);
  746.     break;
  747.       case 'T':            /* possible TEXT, TO */
  748.     if (!strcmp (criteria+1,"EXT"))
  749.       f = nntp_search_string (nntp_search_text,&d,&n);
  750.     else if (!strcmp (criteria+1,"O"))
  751.       f = nntp_search_string (nntp_search_to,&d,&n);
  752.     break;
  753.       case 'U':            /* possible UN* */
  754.     if (criteria[1] == 'N') {
  755.       if (!strcmp (criteria+2,"ANSWERED")) f = nntp_search_unanswered;
  756.       else if (!strcmp (criteria+2,"DELETED")) f = nntp_search_undeleted;
  757.       else if (!strcmp (criteria+2,"FLAGGED")) f = nntp_search_unflagged;
  758.       else if (!strcmp (criteria+2,"KEYWORD"))
  759.         f = nntp_search_flag (nntp_search_unkeyword,&d);
  760.       else if (!strcmp (criteria+2,"SEEN")) f = nntp_search_unseen;
  761.     }
  762.     break;
  763.       default:            /* we will barf below */
  764.     break;
  765.       }
  766.       if (!f) {            /* if can't determine any criteria */
  767.     sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
  768.     mm_log (LOCAL->buf,ERROR);
  769.     return;
  770.       }
  771.                 /* run the search criterion */
  772.       for (i = 1; i <= stream->nmsgs; ++i)
  773.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  774.       mail_elt (stream,i)->searched = NIL;
  775.     }
  776.                 /* report search results to main program */
  777.     for (i = 1; i <= stream->nmsgs; ++i)
  778.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  779.   }
  780. }
  781.  
  782. /* NNTP mail ping mailbox
  783.  * Accepts: MAIL stream
  784.  * Returns: T if stream alive, else NIL
  785.  */
  786.  
  787. long nntp_ping (MAILSTREAM *stream)
  788. {
  789.   /* Kludge alert: SMTPSOFTFATAL is 421 which is used in NNTP to mean ``No
  790.    * next article in this group''.  Hopefully, no NNTP server will send this
  791.    * in response to a STAT */
  792.   return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != SMTPSOFTFATAL);
  793. }
  794.  
  795.  
  796. /* NNTP mail check mailbox
  797.  * Accepts: MAIL stream
  798.  */
  799.  
  800. void nntp_check (MAILSTREAM *stream)
  801. {
  802.                 /* never do if no updates */
  803.   if (LOCAL->dirty) newsrc_write (LOCAL->name,stream,LOCAL->number);
  804.   LOCAL->dirty = NIL;
  805. }
  806.  
  807. /* NNTP mail expunge mailbox
  808.  * Accepts: MAIL stream
  809.  */
  810.  
  811. void nntp_expunge (MAILSTREAM *stream)
  812. {
  813.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  814. }
  815.  
  816.  
  817. /* NNTP mail copy message(s)
  818.  * Accepts: MAIL stream
  819.  *        sequence
  820.  *        destination mailbox
  821.  * Returns: T if copy successful, else NIL
  822.  */
  823.  
  824. long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox)
  825. {
  826.   mm_log ("Copy not valid for NNTP",ERROR);
  827.   return NIL;
  828. }
  829.  
  830.  
  831. /* NNTP mail move message(s)
  832.  * Accepts: MAIL stream
  833.  *        sequence
  834.  *        destination mailbox
  835.  * Returns: T if move successful, else NIL
  836.  */
  837.  
  838. long nntp_move (MAILSTREAM *stream,char *sequence,char *mailbox)
  839. {
  840.   mm_log ("Move not valid for NNTP",ERROR);
  841.   return NIL;
  842. }
  843.  
  844.  
  845. /* NNTP mail append message from stringstruct
  846.  * Accepts: MAIL stream
  847.  *        destination mailbox
  848.  *        stringstruct of messages to append
  849.  * Returns: T if append successful, else NIL
  850.  */
  851.  
  852. long nntp_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
  853.           STRING *message)
  854. {
  855.   mm_log ("Append not valid for NNTP",ERROR);
  856.   return NIL;
  857. }
  858.  
  859. /* NNTP garbage collect stream
  860.  * Accepts: Mail stream
  861.  *        garbage collection flags
  862.  */
  863.  
  864. void nntp_gc (MAILSTREAM *stream,long gcflags)
  865. {
  866.   unsigned long i;
  867.   if (!stream->halfopen)     /* never on half-open stream */
  868.     if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  869.                 /* flush texts from cache */
  870.       for (i = 0; i < stream->nmsgs; i++) {
  871.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  872.     if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  873.       }
  874. }
  875.  
  876. /* Internal routines */
  877.  
  878.  
  879. /* Parse flag list
  880.  * Accepts: MAIL stream
  881.  *        flag list as a character string
  882.  * Returns: flag command list
  883.  */
  884.  
  885. short nntp_getflags (MAILSTREAM *stream,char *flag)
  886. {
  887.   char *t,tmp[MAILTMPLEN],err[MAILTMPLEN];
  888.   short f = 0;
  889.   short i,j;
  890.   if (flag && *flag) {        /* no-op if no flag string */
  891.                 /* check if a list and make sure valid */
  892.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  893.       mm_log ("Bad flag list",ERROR);
  894.       return NIL;
  895.     }
  896.                 /* copy the flag string w/o list construct */
  897.     strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
  898.     tmp[j] = '\0';
  899.     t = ucase (tmp);        /* uppercase only from now on */
  900.  
  901.     while (t && *t) {        /* parse the flags */
  902.       if (*t == '\\') {        /* system flag? */
  903.     switch (*++t) {        /* dispatch based on first character */
  904.     case 'S':        /* possible \Seen flag */
  905.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  906.       t += 4;        /* skip past flag name */
  907.       break;
  908.     case 'D':        /* possible \Deleted flag */
  909.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  910.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  911.       t += 7;        /* skip past flag name */
  912.       break;
  913.     case 'F':        /* possible \Flagged flag */
  914.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  915.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  916.       t += 7;        /* skip past flag name */
  917.       break;
  918.     case 'A':        /* possible \Answered flag */
  919.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  920.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  921.       t += 8;        /* skip past flag name */
  922.       break;
  923.     default:        /* unknown */
  924.       i = 0;
  925.       break;
  926.     }
  927.                 /* add flag to flags list */
  928.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  929.       }
  930.       else {            /* no user flags yet */
  931.     t = strtok (t," ");    /* isolate flag name */
  932.     sprintf (err,"Unknown flag: %.80s",t);
  933.     t = strtok (NIL," ");    /* get next flag */
  934.     mm_log (err,ERROR);
  935.       }
  936.     }
  937.   }
  938.   return f;
  939. }
  940.  
  941. /* Search support routines
  942.  * Accepts: MAIL stream
  943.  *        message number
  944.  *        pointer to additional data
  945.  *        pointer to temporary buffer
  946.  * Returns: T if search matches, else NIL
  947.  */
  948.  
  949. char nntp_search_all (MAILSTREAM *stream,long msgno,char *d,long n)
  950. {
  951.   return T;            /* ALL always succeeds */
  952. }
  953.  
  954.  
  955. char nntp_search_answered (MAILSTREAM *stream,long msgno,char *d,long n)
  956. {
  957.   return mail_elt (stream,msgno)->answered ? T : NIL;
  958. }
  959.  
  960.  
  961. char nntp_search_deleted (MAILSTREAM *stream,long msgno,char *d,long n)
  962. {
  963.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  964. }
  965.  
  966.  
  967. char nntp_search_flagged (MAILSTREAM *stream,long msgno,char *d,long n)
  968. {
  969.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  970. }
  971.  
  972.  
  973. char nntp_search_keyword (MAILSTREAM *stream,long msgno,char *d,long n)
  974. {
  975.   return NIL;            /* keywords not supported yet */
  976. }
  977.  
  978.  
  979. char nntp_search_new (MAILSTREAM *stream,long msgno,char *d,long n)
  980. {
  981.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  982.   return (elt->recent && !elt->seen) ? T : NIL;
  983. }
  984.  
  985. char nntp_search_old (MAILSTREAM *stream,long msgno,char *d,long n)
  986. {
  987.   return mail_elt (stream,msgno)->recent ? NIL : T;
  988. }
  989.  
  990.  
  991. char nntp_search_recent (MAILSTREAM *stream,long msgno,char *d,long n)
  992. {
  993.   return mail_elt (stream,msgno)->recent ? T : NIL;
  994. }
  995.  
  996.  
  997. char nntp_search_seen (MAILSTREAM *stream,long msgno,char *d,long n)
  998. {
  999.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1000. }
  1001.  
  1002.  
  1003. char nntp_search_unanswered (MAILSTREAM *stream,long msgno,char *d,long n)
  1004. {
  1005.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1006. }
  1007.  
  1008.  
  1009. char nntp_search_undeleted (MAILSTREAM *stream,long msgno,char *d,long n)
  1010. {
  1011.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1012. }
  1013.  
  1014.  
  1015. char nntp_search_unflagged (MAILSTREAM *stream,long msgno,char *d,long n)
  1016. {
  1017.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1018. }
  1019.  
  1020.  
  1021. char nntp_search_unkeyword (MAILSTREAM *stream,long msgno,char *d,long n)
  1022. {
  1023.   return T;            /* keywords not supported yet */
  1024. }
  1025.  
  1026.  
  1027. char nntp_search_unseen (MAILSTREAM *stream,long msgno,char *d,long n)
  1028. {
  1029.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1030. }
  1031.  
  1032. char nntp_search_before (MAILSTREAM *stream,long msgno,char *d,long n)
  1033. {
  1034.   return (char) (nntp_msgdate (stream,msgno) < n);
  1035. }
  1036.  
  1037.  
  1038. char nntp_search_on (MAILSTREAM *stream,long msgno,char *d,long n)
  1039. {
  1040.   return (char) (nntp_msgdate (stream,msgno) == n);
  1041. }
  1042.  
  1043.  
  1044. char nntp_search_since (MAILSTREAM *stream,long msgno,char *d,long n)
  1045. {
  1046.                 /* everybody interprets "since" as .GE. */
  1047.   return (char) (nntp_msgdate (stream,msgno) >= n);
  1048. }
  1049.  
  1050.  
  1051. unsigned long nntp_msgdate (MAILSTREAM *stream,long msgno)
  1052. {
  1053.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1054.                 /* get date if don't have it yet */
  1055.   if (!elt->day) nntp_fetchstructure (stream,msgno,NIL);
  1056.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1057. }
  1058.  
  1059.  
  1060. char nntp_search_body (MAILSTREAM *stream,long msgno,char *d,long n)
  1061. {
  1062.   char *t = nntp_fetchtext_work (stream,msgno);
  1063.   return (t && search (t,(unsigned long) strlen (t),d,n));
  1064. }
  1065.  
  1066.  
  1067. char nntp_search_subject (MAILSTREAM *stream,long msgno,char *d,long n)
  1068. {
  1069.   char *t = nntp_fetchstructure (stream,msgno,NIL)->subject;
  1070.   return t ? search (t,strlen (t),d,n) : NIL;
  1071. }
  1072.  
  1073.  
  1074. char nntp_search_text (MAILSTREAM *stream,long msgno,char *d,long n)
  1075. {
  1076.   char *t = nntp_fetchheader (stream,msgno);
  1077.   return (t && search (t,strlen (t),d,n)) ||
  1078.     nntp_search_body (stream,msgno,d,n);
  1079. }
  1080.  
  1081. char nntp_search_bcc (MAILSTREAM *stream,long msgno,char *d,long n)
  1082. {
  1083.   ADDRESS *a = nntp_fetchstructure (stream,msgno,NIL)->bcc;
  1084.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1085.                 /* get text for address */
  1086.   rfc822_write_address (LOCAL->buf,a);
  1087.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1088. }
  1089.  
  1090.  
  1091. char nntp_search_cc (MAILSTREAM *stream,long msgno,char *d,long n)
  1092. {
  1093.   ADDRESS *a = nntp_fetchstructure (stream,msgno,NIL)->cc;
  1094.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1095.                 /* get text for address */
  1096.   rfc822_write_address (LOCAL->buf,a);
  1097.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1098. }
  1099.  
  1100.  
  1101. char nntp_search_from (MAILSTREAM *stream,long msgno,char *d,long n)
  1102. {
  1103.   ADDRESS *a = nntp_fetchstructure (stream,msgno,NIL)->from;
  1104.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1105.                 /* get text for address */
  1106.   rfc822_write_address (LOCAL->buf,a);
  1107.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1108. }
  1109.  
  1110.  
  1111. char nntp_search_to (MAILSTREAM *stream,long msgno,char *d,long n)
  1112. {
  1113.   ADDRESS *a = nntp_fetchstructure (stream,msgno,NIL)->to;
  1114.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1115.                 /* get text for address */
  1116.   rfc822_write_address (LOCAL->buf,a);
  1117.   return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
  1118. }
  1119.  
  1120. /* Search parsers */
  1121.  
  1122.  
  1123. /* Parse a date
  1124.  * Accepts: function to return
  1125.  *        pointer to date integer to return
  1126.  * Returns: function to return
  1127.  */
  1128.  
  1129. search_t nntp_search_date (search_t f,long *n)
  1130. {
  1131.   long i;
  1132.   char *s;
  1133.   MESSAGECACHE elt;
  1134.                 /* parse the date and return fn if OK */
  1135.   return (nntp_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1136.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1137. }
  1138.  
  1139. /* Parse a flag
  1140.  * Accepts: function to return
  1141.  *        pointer to string to return
  1142.  * Returns: function to return
  1143.  */
  1144.  
  1145. search_t nntp_search_flag (search_t f,char **d)
  1146. {
  1147.                 /* get a keyword, return if OK */
  1148.   return (*d = strtok (NIL," ")) ? f : NIL;
  1149. }
  1150.  
  1151.  
  1152. /* Parse a string
  1153.  * Accepts: function to return
  1154.  *        pointer to string to return
  1155.  *        pointer to string length to return
  1156.  * Returns: function to return
  1157.  */
  1158.  
  1159. search_t nntp_search_string (search_t f,char **d,long *n)
  1160. {
  1161.   char *end = " ";
  1162.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1163.   if (!c) return NIL;        /* missing argument */
  1164.   switch (*c) {            /* see what the argument is */
  1165.   case '{':            /* literal string */
  1166.     *n = strtol (c+1,d,10);    /* get its length */
  1167.     if ((*(*d)++ == '}') && (*(*d)++ == '\015') && (*(*d)++ == '\012') &&
  1168.     (!(*(c = *d + *n)) || (*c == ' '))) {
  1169.       char e = *--c;
  1170.       *c = DELIM;        /* make sure not a space */
  1171.       strtok (c," ");        /* reset the strtok mechanism */
  1172.       *c = e;            /* put character back */
  1173.       break;
  1174.     }
  1175.   case '\0':            /* catch bogons */
  1176.   case ' ':
  1177.     return NIL;
  1178.   case '"':            /* quoted string */
  1179.     if (strchr (c+1,'"')) end = "\"";
  1180.     else return NIL;
  1181.   default:            /* atomic string */
  1182.     if (*d = strtok (c,end)) *n = strlen (*d);
  1183.     else return NIL;
  1184.     break;
  1185.   }
  1186.   return f;
  1187. }
  1188.